this 是函数执行上下文(Execution Context)的核心属性,指向调用该函数的对象。但它的指向并非固定,而是由函数的调用方式动态决定
以下内容完全是自(copy)主(from)研(腾讯)发(元宝)
一、this 的绑定规则(核心)
this 的最终指向由函数的调用位置和绑定方式共同决定,主要分为以下 4 种规则:
1. 默认绑定(Default Binding)
当函数独立调用(非对象方法、非构造函数、非显式绑定)时,this 指向全局对象(浏览器为 window,Node.js 为 global)。
严格模式("use strict") 下,this 为 undefined。
示例:
function show() {
// 浏览器:window;
// Node.js:global;
// 严格模式:undefined
console.log(this);
}
// 独立调用 → 默认绑定
show();
2. 隐式绑定(Implicit Binding)
当函数作为对象的方法调用时,this 指向调用该方法的对象(即“所属对象”)。
示例:
const obj = {
name: 'Alice',
greet() {
// this 指向 obj
console.log(`Hello, ${this.name}`);
},
};
// "Hello, Alice"(obj 调用 greet,this 绑定 obj)
obj.greet();
注意:若对象属性被赋值给其他变量,可能丢失隐式绑定:
示例:
const greet = obj.greet;
// 独立调用 → 默认绑定(this 指向全局或 undefined)
greet();
3. 显式绑定(Explicit Binding)
通过 call、apply、bind 方法强制指定 this 的指向。
call:立即调用函数,this指向第一个参数,后续参数为函数参数。apply:立即调用函数,this指向第一个参数,第二个参数为参数数组。bind:返回一个新函数,永久绑定this和部分参数(不立即调用)。
示例:
function say(name, age) {
console.log(`我是${name},今年${age}岁,this是${this.title}`);
}
const person = { title: '工程师' };
// call 显式绑定 this
// "我是张三,今年25岁,this是工程师"
say.call(person, '张三', 25);
// apply 显式绑定 this(参数用数组)
// "我是李四,今年30岁,this是工程师"
say.apply(person, ['李四', 30]);
// bind 永久绑定 this(返回新函数)
const boundSay = say.bind(person);
// "我是王五,今年35岁,this是工程师"
boundSay('王五', 35);
链式绑定:多次 bind 仅第一次生效:
当对一个函数使用 bind 后,返回的是一个 exotic function object (特殊函数对象),它内部有 [[BoundThis]] 和 [[BoundArgs]] 等内部插槽,用于保存绑定的 this 和参数。
示例:
const obj1 = { a: 1 };
const obj2 = { b: 2 };
function foo() {
console.log(this);
}
const boundFoo = foo.bind(obj1).bind(obj2);
// 输出 obj1(仅第一次 bind 生效)
boundFoo();
4. new 绑定(构造函数绑定)
当函数作为构造函数调用(通过 new 关键字)时,this 指向新创建的实例对象。
示例:
function Person(name) {
this.name = name; // this 指向新创建的 Person 实例
}
const alice = new Person('Alice');
console.log(alice.name); // "Alice"
注意:若构造函数返回一个对象(非原始值),this 会被覆盖为返回的对象:
function Dog() {
this.name = '旺财';
// 返回对象 → this 被覆盖
return { name: '小白' };
}
const dog = new Dog();
// "小白"(而非 "旺财")
console.log(dog.name);
二、特殊场景下的 this 指向
1. 箭头函数(Arrow Function)
箭头函数没有自己的 this,其 this 继承自定义时的外层作用域(词法作用域),且无法通过 call/apply/bind 修改。
示例:
const obj = {
name: 'Alice',
greet: () => {
// this 继承自外层(此处可能是全局或 obj 的外层)
console.log(this.name);
},
};
// 若外层是全局,输出 undefined(严格模式)或 window.name
obj.greet();
// 对比普通函数
const obj2 = {
name: 'Bob',
greet() {
// 普通函数,this 指向 obj2
console.log(this.name); // "Bob"
},
};
obj2.greet();
常见用途:在事件回调或嵌套函数中固定 this:
class Counter {
count = 0;
increment() {
setInterval(() => {
// 箭头函数继承 increment 的 this(指向 Counter 实例)
this.count++;
}, 1000);
}
}
2. 事件处理函数中的 this
- DOM 事件监听:回调函数的
this默认指向触发事件的 DOM 元素(除非显式绑定)。
示例:
document.querySelector('button').addEventListener('click', function () {
console.log(this); // 指向被点击的 button 元素
});
- 箭头函数作为事件回调:
this继承自定义时的外层作用域(可能不是 DOM 元素)。
3. 定时器/异步回调中的 this
setTimeout/setInterval:回调函数的this默认指向全局对象(或严格模式的undefined),除非显式绑定。
示例:
const obj = {
name: 'Alice',
greet() {
setTimeout(() => {
// 箭头函数继承 greet 的 this(指向 obj)→ "Alice"
console.log(this.name);
}, 100);
// 普通函数的话:
setTimeout(function () {
// this 指向全局 → undefined(严格模式)
console.log(this.name);
}, 100);
},
};
obj.greet();
4. 嵌套函数中的 this
嵌套函数(如普通函数内部的函数)的 this 通常指向全局对象(或严格模式的 undefined),除非通过闭包保存外层 this。
示例:
const obj = {
name: 'Bob',
outer() {
function inner() {
// 全局对象(非严格模式)或 undefined(严格模式)
console.log(this);
}
inner();
},
};
// 输出非预期的 this
obj.outer();
解决:通过闭包保存外层 this(const that = this)或使用箭头函数:
outer() {
// 保存外层 this
const that = this;
function inner() {
// "Bob"
console.log(that.name);
}
inner();
}
// 或用箭头函数:
outer() {
const inner = () => {
// "Bob"(箭头函数继承 outer 的 this)
console.log(this.name);
};
inner();
}
三、需要避免的 this 陷阱
1. 对象方法赋值给变量后丢失 this
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
},
};
const greet = obj.greet;
greet(); // this 指向全局 → 输出 undefined(非严格模式)
解决:用 bind 绑定 this 或使用箭头函数:
const greet = obj.greet.bind(obj);
// 或定义时用箭头函数:
const objA = {
name: "Tom",
greet: ()=> console.log(this.name)
}
const obj = {
name: "Alice",
// 但更推荐显式绑定或闭包
greet: function() { ... }.bind(this)
};
2. 构造函数中忘记 new 关键字
若构造函数未通过 new 调用,this 会指向全局对象(污染全局):
function Person(name) {
this.name = name; // 未 new 时,this 指向全局 → 全局被污染
}
const p = Person('Alice'); // 全局出现 name: "Alice"
解决:构造函数首字母大写,或在内部检查 this 是否为实例:
function Person(name) {
if (!(this instanceof Person)) {
throw new Error('必须通过 new 调用');
}
this.name = name;
}
3. 箭头函数误用导致 this 错误
箭头函数的 this 不可修改,若在需要动态 this 的场景使用会出错:
const obj = {
name: 'Alice',
greet: () => console.log(this.name),
clickHandler: function () {
// 箭头函数继承 clickHandler 的 this(指向 obj),但如果外层是全局,就会错误
document.querySelector('button').onclick = () => {
console.log(this.name); // 可能不是预期的 obj
};
},
};
解决:在需要动态 this 的场景(如事件回调)使用普通函数,或通过闭包传递 this。
四、总结:this 指向的判断步骤
- 是否通过
new调用? → 是 →this指向新实例。 - 是否通过
call/apply/bind显式绑定? → 是 →this指向绑定的对象。 - 是否作为对象的方法调用? → 是 →
this指向调用该方法的对象。 - 否则 → 默认绑定(全局对象或
undefined,取决于严格模式)。
掌握 this 的绑定规则和常见陷阱,能帮助你写出更健壮、可维护的 JavaScript 代码。关键是根据调用方式和场景,准确判断 this 的指向,必要时通过显式绑定或箭头函数控制其行为。